package io.legado.app.utils

import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import io.legado.app.constant.AppPattern.archiveFileRegex
import io.legado.app.utils.compress.LibArchiveUtils
import splitties.init.appCtx
import java.io.File

/* 自动判断压缩文件后缀 然后再调用具体的实现 */
@Suppress("unused", "MemberVisibilityCanBePrivate")
object ArchiveUtils {

    const val TEMP_FOLDER_NAME = "ArchiveTemp"

    // 临时目录 下次启动自动删除
    val TEMP_PATH: String by lazy {
        appCtx.externalCache.getFile(TEMP_FOLDER_NAME).createFolderReplace().absolutePath
    }

    /**
     * 解压缩档案文件到指定路径，并可选地通过过滤器筛选要解压的文件
     *
     * @param archiveUri 档案文件的URI，用于定位要解压的文件
     * @param path 解压目标路径，默认为临时路径[TEMP_PATH]
     * @param filter 文件过滤器，仅解压满足条件的文件，可为null表示不过滤
     * @return 解压后的文件列表
     */
    fun deCompress(
        archiveUri: Uri,
        path: String = TEMP_PATH,
        filter: ((String) -> Boolean)? = null
    ): List<File> {
        // 调用重载的deCompress函数，使用FileDoc对象封装URI访问细节
        return deCompress(FileDoc.fromUri(archiveUri, false), path, filter)
    }

    fun deCompress(
        archivePath: String,
        path: String = TEMP_PATH,
        filter: ((String) -> Boolean)? = null
    ): List<File> {
        return deCompress(Uri.parse(archivePath), path, filter)
    }

    fun deCompress(
        archiveFile: File,
        path: String = TEMP_PATH,
        filter: ((String) -> Boolean)? = null
    ): List<File> {
        return deCompress(FileDoc.fromFile(archiveFile), path, filter)
    }

    fun deCompress(
        archiveDoc: DocumentFile,
        path: String = TEMP_PATH,
        filter: ((String) -> Boolean)? = null
    ): List<File> {
        return deCompress(FileDoc.fromDocumentFile(archiveDoc), path, filter)
    }

    /**
     * 解压缩文件到指定路径
     *
     * 本函数负责将给定的压缩文件[archiveFileDoc]解压到指定的路径[path]中
     * 如果提供了过滤器[filter]，则只解压符合过滤器条件的文件
     *
     * @param archiveFileDoc 压缩文件的文档对象，用于表示待解压的文件
     * @param path 解压后文件的存放路径，默认为临时路径[TEMP_PATH]
     * @param filter 文件过滤器，用于决定哪些文件需要解压如果为null，则解压所有文件
     * @return 返回解压后的文件列表
     *
     * @throws IllegalArgumentException 如果[archiveFileDoc]是一个目录而不是文件，则抛出此异常
     */
    fun deCompress(
        archiveFileDoc: FileDoc,
        path: String = TEMP_PATH,
        filter: ((String) -> Boolean)? = null
    ): List<File> {
        // 检查输入是否为文件，因为此函数不处理目录
        if (archiveFileDoc.isDir) throw IllegalArgumentException("Unexpected Folder input")

        // 获取压缩文件的名称，用于后续的处理
        val name = archiveFileDoc.name

        // 检查压缩文件的名称是否有效
        checkAchieve(name)

        // 准备一个缓存文件夹，用于存放解压后的文件
        val workPathFileDoc = getCacheFolderFileDoc(name, path)
        val workPath = workPathFileDoc.toString()

        // 打开压缩文件并开始解压过程
        return archiveFileDoc.openReadPfd().getOrThrow().use {
            LibArchiveUtils.unArchive(it, File(workPath), filter)
        }
    }

    /* 遍历目录获取文件名 */
    fun getArchiveFilesName(fileUri: Uri, filter: ((String) -> Boolean)? = null): List<String> =
        getArchiveFilesName(FileDoc.fromUri(fileUri, false), filter)


    /**
     * 获取归档文件中的文件名列表
     *
     * 此函数打开一个归档文件（如ZIP或 TAR），并提取其中包含的文件名列表
     * 可以提供一个可选的过滤器函数来筛选特定的文件
     *
     * @param fileDoc 表示归档文件的文档对象，必须实现 [FileDoc] 接口
     * @param filter 可选参数，一个接受字符串并返回布尔值的函数，用于过滤文件名
     * @return 返回一个字符串列表，包含归档文件中（经过过滤后的）文件名
     */
    fun getArchiveFilesName(
        fileDoc: FileDoc,
        filter: ((String) -> Boolean)? = null
    ): List<String> {
        // 获取文件名并检查是否为归档文件
        val name = fileDoc.name
        checkAchieve(name)

        // 打开文件进行读取，并在使用后自动关闭，以避免资源泄漏
        return fileDoc.openReadPfd().getOrThrow().use {
            try {
                // 使用 LibArchive 工具类提取文件名列表
                LibArchiveUtils.getFilesName(it, filter)
            } catch (e: Exception) {
                // 如果提取过程中发生异常，返回空列表
                emptyList()
            }
        }
    }

    fun isArchive(name: String): Boolean {
        return archiveFileRegex.matches(name)
    }

    private fun checkAchieve(name: String) {
        if (!isArchive(name))
            throw IllegalArgumentException("Unexpected file suffix: Only 7z rar zip Accepted")
    }

    /**
     * 获取缓存文件夹中的文档文件
     * 该方法用于根据存档名称和工作路径构建一个文档文件的路径，并确保该路径存在
     * 如果路径不存在，会尝试创建一个新文件夹
     *
     * @param archiveName 存档名称，用于生成文件夹名称
     * @param workPath 工作路径，表示文件的基路径
     * @return 返回一个表示文档文件的FileDoc对象
     */
    private fun getCacheFolderFileDoc(
        archiveName: String,
        workPath: String
    ): FileDoc {
        // 从工作路径创建一个FileDoc对象，并确保它是一个目录
        return FileDoc.fromUri(Uri.parse(workPath), true)
            // 使用存档名称的MD5哈希值创建或获取一个子文件夹，确保文件夹名称唯一且一致
            .createFolderIfNotExist(MD5Utils.md5Encode16(archiveName))
    }
}